//
// Created by mini on 2021/7/23.
//

import Foundation


public extension YsonValue {

    func toModel<T: Decodable>() -> T? {
        let d = JsonDecoder(value: self)
        do {
            return try T(from: d)
        } catch {
            logd(error)
            return nil
        }
    }

    func toModel<T: Decodable>(_ options: JsonDecoderOptions) -> T? {
        let d = JsonDecoder(value: self, options: options)
        do {
            return try T(from: d)
        } catch {
            logd(error)
            return nil
        }
    }

    func toModel<T: Decodable>(_ block: (JsonDecoderOptions) -> Void) -> T? {
        let d = JsonDecoder(value: self, block)
        do {
            return try T(from: d)
        } catch {
            logd(error)
            return nil
        }
    }
}

public protocol JsonDecoderAction {
    func match(_ type: Any.Type, _ value: YsonValue) -> Bool
    func decodeType(_ type: Any.Type, _ value: YsonValue) -> Any?
}

public struct DateSecondsJsonDecoder: JsonDecoderAction {
    public init() {
    }

    public func match(_ type: Any.Type, _ value: YsonValue) -> Bool {
        (type == Date.self || type == NSDate.self) && (value is YsonNum)
    }

    public func decodeType(_ type: Any.Type, _ value: YsonValue) -> Any? {
        switch value {
        case let yn as YsonNum:
            return Date(timeIntervalSince1970: yn.data.doubleValue)
        default:
            return nil
        }
    }
}

public struct DateMillSecondsJsonDecoder: JsonDecoderAction {
    public init() {
    }

    public func match(_ type: Any.Type, _ value: YsonValue) -> Bool {
        (type == Date.self || type == NSDate.self) && (value is YsonNum)
    }

    public func decodeType(_ type: Any.Type, _ value: YsonValue) -> Any? {
        switch value {
        case let yn as YsonNum:
            return Date(timeIntervalSince1970: yn.data.doubleValue / 1000)
        default:
            return nil
        }
    }
}

public struct DateFormatJsonDecoder: JsonDecoderAction {
    let dateFormat: String

    public init(_ fmt: String) {
        dateFormat = fmt
    }

    public static let timestampX: DateFormatJsonDecoder = DateFormatJsonDecoder("yyyy-MM-dd HH:mm:ss.SSS")
    public static let timestamp: DateFormatJsonDecoder = DateFormatJsonDecoder("yyyy-MM-dd HH:mm:ss")
    public static let date: DateFormatJsonDecoder = DateFormatJsonDecoder("yyyy-MM-dd")
    public static let time: DateFormatJsonDecoder = DateFormatJsonDecoder("HH:mm:ss")
    public static let UTC: DateFormatJsonDecoder = DateFormatJsonDecoder("yyyy-MM-dd'T'HH:mm:ssZ")
    public static let GMT: DateFormatJsonDecoder = DateFormatJsonDecoder("ccc, dd MMM yyyy HH:mm:ss 'GMT'")

    public func match(_ type: Any.Type, _ value: YsonValue) -> Bool {

        let b = (type == Date.self || type == NSDate.self) && (value is YsonString)
        return b
    }

    public func decodeType(_ type: Any.Type, _ value: YsonValue) -> Any? {
        switch value {
        case let ys as YsonString:
            return Date.parse(fmt: dateFormat, value: ys.data)
        default:
            return nil
        }
    }
}

public class JsonDecoderOptions: Applyable {
    fileprivate var decoders: [JsonDecoderAction] = []
    //fieldName -> JsonName
    fileprivate var namesMap: [String: String] = [:]
    public var basicDecoder: JsonBasicDecoder = JsonBasicDecoderImpl()

    public init() {

    }

    @discardableResult
    public func nameMap(field: String, json: String) -> JsonDecoderOptions {
        namesMap[field] = json
        return self
    }

    @discardableResult
    public func push(_ d: JsonDecoderAction) -> JsonDecoderOptions {
        decoders.insert(d, at: 0)
        return self
    }
    public func dateFormat(_ fmt:String){
        push(DateFormatJsonDecoder(fmt))
    }
    public func dateSeconds1970(){
        push(DateSecondsJsonDecoder())
    }
    public func dateMillSeconds1970(){
        push(DateMillSecondsJsonDecoder())
    }
    fileprivate func decode(_ type: Any.Type, _ value: YsonValue) -> (Bool, Any?) {
        for d in decoders {
            if (d.match(type, value)) {
                return (true, d.decodeType(type, value))
            }
        }
        return (false, nil)
    }
}

public protocol JsonBasicDecoder {
    func decode(_ type: Bool.Type, _ jsonValue: YsonValue) throws -> Bool
    func decode(_ type: String.Type, _ jsonValue: YsonValue) throws -> String
    func decode(_ type: Double.Type, _ jsonValue: YsonValue) throws -> Double
    func decode(_ type: Int64.Type, _ jsonValue: YsonValue) throws -> Int64
    func decode(_ type: UInt64.Type, _ jsonValue: YsonValue) throws -> UInt64
}

fileprivate extension JsonBasicDecoder {


    func decodeOpt(_ type: String.Type, _ jsonValue: YsonValue) throws -> String? {
        if jsonValue is YsonNull {
            return nil
        }
        return try decode(String.self, jsonValue)
    }

    func decodeOpt(_ type: Bool.Type, _ jsonValue: YsonValue) throws -> Bool? {
        if jsonValue is YsonNull {
            return nil
        }
        return try decode(Bool.self, jsonValue)
    }

    func decode(_ type: Float.Type, _ jsonValue: YsonValue) throws -> Float {
        try decode(Double.self, jsonValue).floatValue
    }

    func decodeOpt(_ type: Float.Type, _ jsonValue: YsonValue) throws -> Float? {
        if jsonValue is YsonNull {
            return nil
        }
        return try decode(Double.self, jsonValue).floatValue
    }

    func decodeOpt(_ type: Double.Type, _ jsonValue: YsonValue) throws -> Double? {
        if jsonValue is YsonNull {
            return nil
        }
        return try decode(Double.self, jsonValue)
    }

    func decode(_ type: Int.Type, _ jsonValue: YsonValue) throws -> Int {
        try decode(Int64.self, jsonValue).intValue
    }

    func decode(_ type: UInt.Type, _ jsonValue: YsonValue) throws -> UInt {
        try decode(Int64.self, jsonValue).uintValue
    }

    func decode(_ type: Int8.Type, _ jsonValue: YsonValue) throws -> Int8 {
        try decode(Int64.self, jsonValue).int8Value
    }

    func decode(_ type: Int16.Type, _ jsonValue: YsonValue) throws -> Int16 {
        try decode(Int64.self, jsonValue).int16Value
    }

    func decode(_ type: Int32.Type, _ jsonValue: YsonValue) throws -> Int32 {
        try decode(Int64.self, jsonValue).int32Value
    }

    func decode(_ type: UInt8.Type, _ jsonValue: YsonValue) throws -> UInt8 {
        try decode(UInt64.self, jsonValue).uint8Value
    }

    func decode(_ type: UInt16.Type, _ jsonValue: YsonValue) throws -> UInt16 {
        try decode(UInt64.self, jsonValue).uint16Value
    }

    func decode(_ type: UInt32.Type, _ jsonValue: YsonValue) throws -> UInt32 {
        try decode(UInt64.self, jsonValue).uint32Value
    }

    func decodeOpt(_ type: Int.Type, _ jsonValue: YsonValue) throws -> Int? {
        if jsonValue is YsonNull {
            return nil
        }
        return try decode(Int64.self, jsonValue).intValue
    }

    func decodeOpt(_ type: UInt.Type, _ jsonValue: YsonValue) throws -> UInt? {
        if jsonValue is YsonNull {
            return nil
        }
        return try decode(UInt64.self, jsonValue).uintValue
    }

    func decodeOpt(_ type: Int8.Type, _ jsonValue: YsonValue) throws -> Int8? {
        if jsonValue is YsonNull {
            return nil
        }
        return try decode(Int64.self, jsonValue).int8Value
    }

    func decodeOpt(_ type: Int16.Type, _ jsonValue: YsonValue) throws -> Int16? {
        if jsonValue is YsonNull {
            return nil
        }
        return try decode(Int64.self, jsonValue).int16Value
    }

    func decodeOpt(_ type: Int32.Type, _ jsonValue: YsonValue) throws -> Int32? {
        if jsonValue is YsonNull {
            return nil
        }
        return try decode(Int64.self, jsonValue).int32Value
    }

    func decodeOpt(_ type: Int64.Type, _ jsonValue: YsonValue) throws -> Int64? {
        if jsonValue is YsonNull {
            return nil
        }
        return try decode(Int64.self, jsonValue)
    }

    func decodeOpt(_ type: UInt8.Type, _ jsonValue: YsonValue) throws -> UInt8? {
        if jsonValue is YsonNull {
            return nil
        }
        return try decode(UInt64.self, jsonValue).uint8Value
    }

    func decodeOpt(_ type: UInt16.Type, _ jsonValue: YsonValue) throws -> UInt16? {
        if jsonValue is YsonNull {
            return nil
        }
        return try decode(UInt64.self, jsonValue).uint16Value
    }

    func decodeOpt(_ type: UInt32.Type, _ jsonValue: YsonValue) throws -> UInt32? {
        if jsonValue is YsonNull {
            return nil
        }
        return try decode(UInt64.self, jsonValue).uint32Value
    }

    func decodeOpt(_ type: UInt64.Type, _ jsonValue: YsonValue) throws -> UInt64? {
        if jsonValue is YsonNull {
            return nil
        }
        return try decode(UInt64.self, jsonValue)
    }
}

public class JsonBasicDecoderImpl: JsonBasicDecoder {
    public func decode(_ type: Bool.Type, _ jsonValue: YsonValue) throws -> Bool {
        switch jsonValue {
        case _ as YsonNull: return false
        case let y as YsonBool: return y.data
        case let y as YsonNum: return y.data == 1
        case let y as YsonString: return y.data == "true"
        case _ as YsonBlob: return false
        case let y as YsonArray: return y.data.count > 1
        case let y as YsonObject: return y.data.count > 1
        default:return false
        }
    }

    public func decode(_ type: String.Type, _ jsonValue: YsonValue) throws -> String {
        switch jsonValue {
        case _ as YsonNull: return ""
        case let y as YsonBool: return "\(y.data)"
        case let y as YsonNum: return "\(y.data)"
        case let y as YsonString: return "\(y.data)"
        case let y as YsonBlob: return y.data.base64
        case let y as YsonArray: return y.yson
        case let y as YsonObject: return y.yson
        default:return ""
        }
    }

    public func decode(_ type: Double.Type, _ jsonValue: YsonValue) throws -> Double {
        switch jsonValue {
        case _ as YsonNull: return 0
        case let y as YsonBool: return y.data ? 1 : 0
        case let y as YsonNum: return y.data.doubleValue
        case let y as YsonString: return y.data.toDouble ?? 0
        case _ as YsonBlob: return 0
        case _ as YsonArray: return 0
        case _ as YsonObject: return 0
        default:return 0
        }
    }

    public func decode(_ type: Int64.Type, _ jsonValue: YsonValue) throws -> Int64 {
        switch jsonValue {
        case _ as YsonNull: return 0
        case let y as YsonBool: return y.data ? 1 : 0
        case let y as YsonNum: return y.data.int64Value
        case let y as YsonString: return y.data.toInt64 ?? 0
        case _ as YsonBlob: return 0
        case _ as YsonArray: return 0
        case _ as YsonObject: return 0
        default:return 0
        }
    }

    public func decode(_ type: UInt64.Type, _ jsonValue: YsonValue) throws -> UInt64 {
        switch jsonValue {
        case _ as YsonNull: return 0
        case let y as YsonBool: return y.data ? 1 : 0
        case let y as YsonNum: return y.data.uint64Value
        case let y as YsonString: return y.data.toUInt64 ?? 0
        case _ as YsonBlob: return 0
        case _ as YsonArray: return 0
        case _ as YsonObject: return 0
        default:return 0
        }
    }
}

public class JsonDecoder: Decoder {
    public var codingPath: [CodingKey] = []
    public var userInfo: [CodingUserInfoKey: Any] = [:]
    var options: JsonDecoderOptions
    var ysonValue: YsonValue

    public init(value: YsonValue, options: JsonDecoderOptions = JsonDecoderOptions()) {
        ysonValue = value
        self.options = options
    }

    public convenience init(value: YsonValue, _ block: (JsonDecoderOptions) -> Void) {
        self.init(value: value)
        block(self.options)
    }

    public func container<Key>(keyedBy type: Key.Type) throws -> KeyedDecodingContainer<Key> where Key: CodingKey {
        KeyedDecodingContainer<Key>(ObjectDecoder(value: ysonValue as! YsonObject, options: self.options))
    }

    public func unkeyedContainer() throws -> UnkeyedDecodingContainer {
        if let ya = ysonValue as? YsonArray {
            return ArrayDecoder(value: ya, options: self.options)
        }
        throw  typeMismatch(YsonArray.self, self.codingPath)
    }

    public func singleValueContainer() throws -> SingleValueDecodingContainer {
        ValueDecoder(value: ysonValue, options: self.options)
    }
}

class ObjectDecoder<K: CodingKey>: KeyedDecodingContainerProtocol {
    typealias Key = K
    var codingPath: [CodingKey] = []
    var userInfo: [CodingUserInfoKey: Any] = [:]
    let options: JsonDecoderOptions
    var jsonObject: YsonObject

    init(value: YsonObject, options: JsonDecoderOptions) {
        jsonObject = value
        self.options = options
    }

    private func pick(_ key: Key) -> YsonValue {
        return jsonObject[keyName(key)]
    }

    private func keyName(_ key: Key) -> String {
        options.namesMap[key.stringValue] ?? key.stringValue
    }

    public var allKeys: [Key] {
        return jsonObject.keys.compactMap {
            Key(stringValue: $0)
        }
    }

    func contains(_ key: Key) -> Bool {
        jsonObject.has(keyName(key))
    }

    func decodeNil(forKey key: Key) throws -> Bool {
        jsonObject[key.stringValue] is YsonNull
    }


    func decodeIfPresent(_ type: Bool.Type, forKey key: Key) throws -> Bool? {
        try options.basicDecoder.decodeOpt(type, pick(key))
    }

    func decodeIfPresent(_ type: String.Type, forKey key: Key) throws -> String? {
        try options.basicDecoder.decodeOpt(type, pick(key))
    }

    func decodeIfPresent(_ type: Double.Type, forKey key: Key) throws -> Double? {
        try options.basicDecoder.decodeOpt(type, pick(key))
    }

    func decodeIfPresent(_ type: Float.Type, forKey key: Key) throws -> Float? {
        try options.basicDecoder.decodeOpt(type, pick(key))
    }

    func decodeIfPresent(_ type: Int.Type, forKey key: Key) throws -> Int? {
        try options.basicDecoder.decodeOpt(type, pick(key))
    }

    func decodeIfPresent(_ type: Int8.Type, forKey key: Key) throws -> Int8? {
        try options.basicDecoder.decodeOpt(type, pick(key))
    }

    func decodeIfPresent(_ type: Int16.Type, forKey key: Key) throws -> Int16? {
        try options.basicDecoder.decodeOpt(type, pick(key))
    }

    func decodeIfPresent(_ type: Int32.Type, forKey key: Key) throws -> Int32? {
        try options.basicDecoder.decodeOpt(type, pick(key))
    }

    func decodeIfPresent(_ type: Int64.Type, forKey key: Key) throws -> Int64? {
        try options.basicDecoder.decodeOpt(type, pick(key))
    }

    func decodeIfPresent(_ type: UInt.Type, forKey key: Key) throws -> UInt? {
        try options.basicDecoder.decodeOpt(type, pick(key))
    }

    func decodeIfPresent(_ type: UInt8.Type, forKey key: Key) throws -> UInt8? {
        try options.basicDecoder.decodeOpt(type, pick(key))
    }

    func decodeIfPresent(_ type: UInt16.Type, forKey key: Key) throws -> UInt16? {
        try options.basicDecoder.decodeOpt(type, pick(key))
    }

    func decodeIfPresent(_ type: UInt32.Type, forKey key: Key) throws -> UInt32? {
        try options.basicDecoder.decodeOpt(type, pick(key))
    }

    func decodeIfPresent(_ type: UInt64.Type, forKey key: Key) throws -> UInt64? {
        try options.basicDecoder.decodeOpt(type, pick(key))
    }

    func decodeIfPresent<T>(_ type: T.Type, forKey key: Key) throws -> T? where T: Decodable {
        contains(key) ? try decode(type, forKey: key) : nil
    }

    func decode(_ type: Bool.Type, forKey key: Key) throws -> Bool {
        try options.basicDecoder.decode(type, pick(key))
    }

    func decode(_ type: String.Type, forKey key: Key) throws -> String {
        try options.basicDecoder.decode(type, pick(key))
    }

    func decode(_ type: Double.Type, forKey key: Key) throws -> Double {
        try options.basicDecoder.decode(type, pick(key))
    }

    func decode(_ type: Float.Type, forKey key: Key) throws -> Float {
        try options.basicDecoder.decode(type, pick(key))
    }

    func decode(_ type: Int.Type, forKey key: Key) throws -> Int {
        try options.basicDecoder.decode(type, pick(key))
    }

    func decode(_ type: Int8.Type, forKey key: Key) throws -> Int8 {
        try options.basicDecoder.decode(type, pick(key))
    }

    func decode(_ type: Int16.Type, forKey key: Key) throws -> Int16 {
        try options.basicDecoder.decode(type, pick(key))
    }

    func decode(_ type: Int32.Type, forKey key: Key) throws -> Int32 {
        try options.basicDecoder.decode(type, pick(key))
    }

    func decode(_ type: Int64.Type, forKey key: Key) throws -> Int64 {
        try options.basicDecoder.decode(type, pick(key))
    }

    func decode(_ type: UInt.Type, forKey key: Key) throws -> UInt {
        try options.basicDecoder.decode(type, pick(key))
    }

    func decode(_ type: UInt8.Type, forKey key: Key) throws -> UInt8 {
        try options.basicDecoder.decode(type, pick(key))
    }

    func decode(_ type: UInt16.Type, forKey key: Key) throws -> UInt16 {
        try options.basicDecoder.decode(type, pick(key))
    }

    func decode(_ type: UInt32.Type, forKey key: Key) throws -> UInt32 {
        try options.basicDecoder.decode(type, pick(key))
    }

    func decode(_ type: UInt64.Type, forKey key: Key) throws -> UInt64 {
        try options.basicDecoder.decode(type, pick(key))
    }

    func decode<T>(_ type: T.Type, forKey key: Key) throws -> T where T: Decodable {
        try decodeOf(type, forKey: key)
    }

    func decodeOf<T>(_ type: T.Type, forKey key: Key) throws -> T where T: Decodable {
        let yv = pick(key)
        let p = options.decode(type, yv)
        if p.0 {
            return p.1 as! T
        }
        do {
            return try T(from: JsonDecoder(value: yv, options: self.options))
        } catch (let e) {
            loge("decode \(keyName(key)) error")
            throw e
        }
    }

    func nestedContainer<NestedKey>(keyedBy type: NestedKey.Type, forKey key: Key) throws -> KeyedDecodingContainer<NestedKey> where NestedKey: CodingKey {
        if let yv = pick(key) as? YsonObject {
            return KeyedDecodingContainer<NestedKey>(ObjectDecoder<NestedKey>(value: yv, options: self.options))
        }
        throw typeMismatch(YsonObject.self, self.codingPath)
    }

    func nestedUnkeyedContainer(forKey key: Key) throws -> UnkeyedDecodingContainer {
        if let ya = pick(key) as? YsonArray {
            return ArrayDecoder(value: ya, options: self.options)
        }
        throw typeMismatch(YsonArray.self, self.codingPath)
    }

    func superDecoder() throws -> Decoder {
        JsonDecoder(value: jsonObject, options: self.options)
    }

    func superDecoder(forKey key: Key) throws -> Decoder {
        JsonDecoder(value: jsonObject, options: self.options)
    }
}

class ArrayDecoder: UnkeyedDecodingContainer {
    var jsonArray: YsonArray
    var codingPath: [CodingKey] = []
    var userInfo: [CodingUserInfoKey: Any] = [:]
    var options: JsonDecoderOptions

    var currentIndex: Int = 0
    var count: Int? {
        jsonArray.count
    }
    var isAtEnd: Bool {
        currentIndex >= count!
    }


    init(value: YsonArray, options: JsonDecoderOptions) {
        jsonArray = value
        self.options = options
    }

    private func current() -> YsonValue {
        jsonArray[currentIndex]
    }

    func decodeNil() throws -> Bool {
        return current() is YsonNull
    }


    func decodeIfPresent(_ type: Int.Type) throws -> Int? {
        try options.basicDecoder.decodeOpt(type, pickValue())
    }

    func decodeIfPresent(_ type: Bool.Type) throws -> Bool? {
        try options.basicDecoder.decodeOpt(type, pickValue())
    }

    func decodeIfPresent(_ type: String.Type) throws -> String? {
        try options.basicDecoder.decodeOpt(type, pickValue())
    }

    func decodeIfPresent(_ type: Double.Type) throws -> Double? {
        try options.basicDecoder.decodeOpt(type, pickValue())
    }

    func decodeIfPresent(_ type: Float.Type) throws -> Float? {
        try options.basicDecoder.decodeOpt(type, pickValue())
    }

    func decodeIfPresent(_ type: Int8.Type) throws -> Int8? {
        try options.basicDecoder.decodeOpt(type, pickValue())
    }

    func decodeIfPresent(_ type: Int16.Type) throws -> Int16? {
        try options.basicDecoder.decodeOpt(type, pickValue())
    }

    func decodeIfPresent(_ type: Int32.Type) throws -> Int32? {
        try options.basicDecoder.decodeOpt(type, pickValue())
    }

    func decodeIfPresent(_ type: Int64.Type) throws -> Int64? {
        try options.basicDecoder.decodeOpt(type, pickValue())
    }

    func decodeIfPresent(_ type: UInt.Type) throws -> UInt? {
        try options.basicDecoder.decodeOpt(type, pickValue())
    }

    func decodeIfPresent(_ type: UInt8.Type) throws -> UInt8? {
        try options.basicDecoder.decodeOpt(type, pickValue())
    }

    func decodeIfPresent(_ type: UInt16.Type) throws -> UInt16? {
        try options.basicDecoder.decodeOpt(type, pickValue())
    }

    func decodeIfPresent(_ type: UInt32.Type) throws -> UInt32? {
        try options.basicDecoder.decodeOpt(type, pickValue())
    }

    func decodeIfPresent(_ type: UInt64.Type) throws -> UInt64? {
        try options.basicDecoder.decodeOpt(type, pickValue())
    }

    func decodeIfPresent<T>(_ type: T.Type) throws -> T? where T: Decodable {
        if current() is YsonNull {
            currentIndex += 1
            return nil
        }
        return try decode(type)
    }

    func decode(_ type: Bool.Type) throws -> Bool {
        try options.basicDecoder.decode(type, pickValue())
    }

    func decode(_ type: String.Type) throws -> String {
        try options.basicDecoder.decode(type, pickValue())
    }

    func decode(_ type: Double.Type) throws -> Double {
        try options.basicDecoder.decode(type, pickValue())
    }

    func decode(_ type: Float.Type) throws -> Float {
        try options.basicDecoder.decode(type, pickValue())
    }

    func decode(_ type: Int.Type) throws -> Int {
        try options.basicDecoder.decode(type, pickValue())
    }

    func decode(_ type: Int8.Type) throws -> Int8 {
        try options.basicDecoder.decode(type, pickValue())
    }

    func decode(_ type: Int16.Type) throws -> Int16 {
        try options.basicDecoder.decode(type, pickValue())
    }

    func decode(_ type: Int32.Type) throws -> Int32 {
        try options.basicDecoder.decode(type, pickValue())
    }

    func decode(_ type: Int64.Type) throws -> Int64 {
        try options.basicDecoder.decode(type, pickValue())
    }

    func decode(_ type: UInt.Type) throws -> UInt {
        try options.basicDecoder.decode(type, pickValue())
    }

    func decode(_ type: UInt8.Type) throws -> UInt8 {
        try options.basicDecoder.decode(type, pickValue())
    }

    func decode(_ type: UInt16.Type) throws -> UInt16 {
        try options.basicDecoder.decode(type, pickValue())
    }

    func decode(_ type: UInt32.Type) throws -> UInt32 {
        try options.basicDecoder.decode(type, pickValue())
    }

    func decode(_ type: UInt64.Type) throws -> UInt64 {
        try options.basicDecoder.decode(type, pickValue())
    }

    func decode<T>(_ type: T.Type) throws -> T where T: Decodable {
        try decodeOf(type)
    }

    fileprivate func pickValue() -> YsonValue {
        let yo = jsonArray[currentIndex]
        currentIndex += 1
        return yo
    }

    func decodeOf<T>(_ type: T.Type) throws -> T where T: Decodable {
        let yv = pickValue()
        let p = options.decode(type, yv)
        if p.0 {
            return p.1 as! T
        }
        return try T(from: JsonDecoder(value: yv, options: self.options))
    }

    func nestedContainer<NestedKey>(keyedBy type: NestedKey.Type) throws -> KeyedDecodingContainer<NestedKey> where NestedKey: CodingKey {
        if let yo = pickValue() as? YsonObject {
            return KeyedDecodingContainer<NestedKey>(ObjectDecoder<NestedKey>(value: yo, options: self.options))
        }
        throw typeMismatch(YsonObject.self, self.codingPath)
    }

    func nestedUnkeyedContainer() throws -> UnkeyedDecodingContainer {
        if let ya = pickValue() as? YsonArray {
            return ArrayDecoder(value: ya, options: self.options)
        }
        throw typeMismatch(YsonArray.self, self.codingPath)
    }

    func superDecoder() throws -> Decoder {
        JsonDecoder(value: jsonArray, options: self.options)
    }
}

class ValueDecoder: SingleValueDecodingContainer {
    var jsonValue: YsonValue
    var codingPath: [CodingKey] = []
    var userInfo: [CodingUserInfoKey: Any] = [:]
    let options: JsonDecoderOptions

    init(value: YsonValue, options: JsonDecoderOptions) {
        jsonValue = value
        self.options = options
    }

    func decodeNil() -> Bool {
        jsonValue is YsonNull
    }

    func decode(_ type: Bool.Type) throws -> Bool {
        try options.basicDecoder.decode(type, jsonValue)
    }

    func decode(_ type: String.Type) throws -> String {
        try options.basicDecoder.decode(type, jsonValue)
    }

    func decode(_ type: Double.Type) throws -> Double {
        try options.basicDecoder.decode(type, jsonValue)
    }

    func decode(_ type: Float.Type) throws -> Float {
        try options.basicDecoder.decode(type, jsonValue)
    }

    func decode(_ type: Int.Type) throws -> Int {
        try options.basicDecoder.decode(type, jsonValue)
    }

    func decode(_ type: Int8.Type) throws -> Int8 {
        try options.basicDecoder.decode(type, jsonValue)
    }

    func decode(_ type: Int16.Type) throws -> Int16 {
        try options.basicDecoder.decode(type, jsonValue)
    }

    func decode(_ type: Int32.Type) throws -> Int32 {
        try options.basicDecoder.decode(type, jsonValue)
    }

    func decode(_ type: Int64.Type) throws -> Int64 {
        try options.basicDecoder.decode(type, jsonValue)
    }

    func decode(_ type: UInt.Type) throws -> UInt {
        try options.basicDecoder.decode(type, jsonValue)
    }

    func decode(_ type: UInt8.Type) throws -> UInt8 {
        try options.basicDecoder.decode(type, jsonValue)
    }

    func decode(_ type: UInt16.Type) throws -> UInt16 {
        try options.basicDecoder.decode(type, jsonValue)
    }

    func decode(_ type: UInt32.Type) throws -> UInt32 {
        try options.basicDecoder.decode(type, jsonValue)
    }

    func decode(_ type: UInt64.Type) throws -> UInt64 {
        try options.basicDecoder.decode(type, jsonValue)
    }

    func decode<T>(_ type: T.Type) throws -> T where T: Decodable {
        let p = options.decode(type, self.jsonValue)
        if p.0 {
            return p.1 as! T
        }
        return try T(from: JsonDecoder(value: self.jsonValue, options: self.options))
    }
}

fileprivate func typeMismatch(_ type: Any.Type, _ path: [CodingKey], _ desc: String = "Type Mismatch") -> Error {
    DecodingError.typeMismatch(type, DecodingError.Context(codingPath: path, debugDescription: desc))
}

fileprivate func valueNotFound(_ type: Any.Type, _ path: [CodingKey], _ desc: String = "Value NOT Found") -> Error {
    DecodingError.valueNotFound(type, DecodingError.Context(codingPath: path, debugDescription: desc))
}